By: Allyson Lynch

In [1]:
import pandas as pd
import urllib.request 
import urllib.error
import urllib.parse
from rdkit import Chem 
import numpy as np
import statistics

Creates a file that cleans the experimental smiles names

In [2]:
def cactus(amine):
    """takes an amine name and calls a smiles string from cactus"""
    compliant_name = urllib.parse.quote_plus(amine)
    url = "https://cactus.nci.nih.gov/chemical/structure/"+amine+"/"+"smiles"
    req = urllib.request.Request(url)
    try: resp = urllib.request.urlopen(req)
    except urllib.error.URLError as e:
        resp = None
    if resp=="" or resp==None:
        resp = amine
    else:
        resp = resp.read().decode(resp.headers.get_content_charset() or 'utf-8')
        if resp.find("!")!=-1: #cannot convert from rdkit smiles back to cactus smiles
            resp = amine
    return resp

def check_name_cactus(name):
    """takes an amine name and returns a neutralized and canonicalized smiles string"""
    resp = cactus(name) #pull name from cactus
    mol = Chem.MolFromSmiles(resp) #canonicalize
    if mol!=None:
        resp = Chem.MolToSmiles(mol, isomericSmiles=False)
    final = cactus(resp) #re-run through cactus to clean
    return final  

def clean_salts(outcomes_file):
    """takes a file of outcomes and cleans the smiles string"""
    smiles_df = pd.read_table(outcomes_file)#, usecols=[i for i in range(17)]) #read file
    smiles_df['Smiles'] = smiles_df['_raw_SMILES.nosalt'].apply(check_name_cactus)
    smiles_df.to_csv("Outputs/03 Outcomes/"+outcomes_file[18:-12]+"_clean.tsv", sep='\t') #save as tsv

Categorizes each amine by success and success rate

In [3]:
def outcome_success(outcomes_file):
    """takes a file of outcomes and returns a file of success and success rates per smiles"""
    smiles_df = pd.read_table(outcomes_file) #read file
    outcome_df = pd.DataFrame(smiles_df.groupby('Smiles')['_out_Outcome'].max()) #success number per smiles
    clean_df = outcome_df.rename(columns={'_out_Outcome':'OUT_anySuccess?'}).replace({4:True, 1:False, 2:False, 3:False}) #clean
    count_df = pd.DataFrame(smiles_df.groupby('Smiles')['Smiles'].count()).rename(columns={'Smiles':'count'})  #count occurances
    count_4_df = smiles_df.loc[smiles_df['_out_Outcome']==4] #only keep 4s
    count_4_df = pd.DataFrame(count_4_df.groupby('Smiles')['Smiles'].count()).rename(columns={'Smiles':'count_4'}) #count 4 occurances
    count_df = pd.merge(count_df, count_4_df, left_index=True, right_index=True, how='outer').fillna(0)
    clean_df['OUT_successRate'] = count_df['count_4']/count_df['count'] #calculate success rate
    clean_df.to_csv("Outputs/03 Outcomes/"+outcomes_file[20:-10]+"_outcomestats.tsv", sep='\t') #save as tsv

Calculates the probability of popular and unpouplar amines success and failure

In [20]:
def bootstrap(l1, nrepeat=10000): #calculate error bars
    sample = [sum(np.random.choice(l1, size=len(l1))) for i in range(nrepeat)] #sum of lists by random sample
    r = [i/len(l1) for i in sample] #probability
    return [statistics.mean(r), statistics.stdev(r)]

def shorten(stdinchikey):
    inchikey = stdinchikey[:23]
    return inchikey

def success_popularity(success_file, success_rate=False):
    """takes a file of outcome success data and popularity and returns a file that calculates a number success per amine, probability, and bootstrap error"""
    success_df = pd.read_table(success_file) #read file
    inchi_df = pd.read_table("Outputs/01 CCDC Smiles File/Input Files/standardize_names.tsv") #read file
    success_df = pd.merge(success_df, inchi_df, left_on='Smiles', right_on='smiles') 
    success_df['inchikey'] = success_df['stdinchikey'].apply(shorten) #add inchi key column
    popularity_df = pd.read_table("Outputs/02 Popularity Files/amine_popularity.tsv") 
    success_popularity = pd.merge(popularity_df, success_df, on='inchikey', how='right').fillna(-1)
    final_df = pd.DataFrame({'popularity':['Popular', 'NotPopular', 'Unpopular', 'Absent']})
    for oxide in ["metaloxides_popular", "metalborates_popular"]:
        sp = success_popularity.loc[(success_popularity[oxide]==1) & (success_popularity['OUT_anySuccess?']==True)]
        fp = success_popularity.loc[(success_popularity[oxide]==1) & (success_popularity['OUT_anySuccess?']==False)]
        sn = success_popularity.loc[(success_popularity[oxide]<=0) & (success_popularity['OUT_anySuccess?']==True)]
        fn = success_popularity.loc[(success_popularity[oxide]<=0) & (success_popularity['OUT_anySuccess?']==False)]
        su = success_popularity.loc[(success_popularity[oxide]==0) & (success_popularity['OUT_anySuccess?']==True)]
        fu = success_popularity.loc[(success_popularity[oxide]==0) & (success_popularity['OUT_anySuccess?']==False)]
        sa = success_popularity.loc[(success_popularity[oxide]==-1) & (success_popularity['OUT_anySuccess?']==True)]
        fa = success_popularity.loc[(success_popularity[oxide]==-1) & (success_popularity['OUT_anySuccess?']==False)]
        popular = len(sp['OUT_successRate'].tolist() + fp['OUT_successRate'].tolist())
        notpopular = len(sn['OUT_successRate'].tolist() + fn['OUT_successRate'].tolist())
        unpopular = len(su['OUT_successRate'].tolist() + fu['OUT_successRate'].tolist())
        absent = len(sa['OUT_successRate'].tolist() + fa['OUT_successRate'].tolist())
        if success_rate==False:
            success_popularity.loc[success_popularity['OUT_anySuccess?'] == True, 'OUT_anySuccess?'] = 1 #replace True/False with 1/0
            success_popularity.loc[success_popularity['OUT_anySuccess?'] == False, 'OUT_anySuccess?'] = 0  
        populars = bootstrap(sp['OUT_successRate'].tolist() + fp['OUT_successRate'].tolist())[1]
        notpopulars = bootstrap(sn['OUT_successRate'].tolist() + fn['OUT_successRate'].tolist())[1]
        unpopulars = bootstrap(su['OUT_successRate'].tolist() + fu['OUT_successRate'].tolist())[1]
        absents = bootstrap(sa['OUT_successRate'].tolist() + fa['OUT_successRate'].tolist())[1]
        final_df['number_'+oxide[:-9]] = [len(sp.index.tolist()), len(fp.index.tolist()), len(sn.index.tolist()), len(fn.index.tolist()), len(su.index.tolist()), len(fu.index.tolist()), len(sa.index.tolist()), len(fa.index.tolist())]
        final_df['probability_'+oxide[:-9]] = [len(sp.index.tolist())/popular, len(fp.index.tolist())/popular, len(sn.index.tolist())/notpopular, len(fn.index.tolist())/notpopular, len(su.index.tolist())/unpopular, len(fu.index.tolist())/unpopular, len(sa.index.tolist())/absent, len(fa.index.tolist())/absent]
        final_df['error_'+oxide[:-9]] = [populars, populars, notpopulars, notpopulars, unpopulars, unpopulars, absents, absents]
    final_df.to_csv("Outputs/03 Outcomes/success_popularity.tsv", sep='\t') #save as tsv

Calculates basic statistics of the outcomes

In [16]:
def stats(outcomes_file, include_absent_amines=False):
    """takes a popularity data file, outcomes data file, and optional parameter for including not popular (-1) and returns a table of descriptive statistics with columns for each molecule by popularity"""
    df_popularity = pd.read_table("Outputs/02 Popularity Files/amine_popularity.tsv")
    df_properties = pd.read_table(outcomes_file)
    properties = ["mean", "median", "stdev"]
    popularity_properties = pd.merge(df_popularity, df_properties, left_on='smiles', right_on='Smiles', how='right').fillna(-1) #merge popularity and properties by smiles
    final_df = pd.DataFrame({'properties':properties}, index=properties) #initiliaze dataframe
    #make a list per molecule...
    for molecule in ["metalborates_popular"]:
        #make popular and unpopular data frames
        popular_df = popularity_properties.loc[popularity_properties[molecule]==1] #popular dataframe
        popular = popular_df['_out_Outcome'].tolist()
        if include_absent_amines: 
            unpopular_df = popularity_properties.loc[popularity_properties[molecule] <= 0] #unpopular + absent dataframe
        else:
            unpopular_df = popularity_properties.loc[popularity_properties[molecule]==0] #unpopular dataframe
        unpopular = unpopular_df['_out_Outcome'].tolist()
        #...of the stats for each property
        means = [statistics.mean(popular), statistics.mean(unpopular)]
        medians = [statistics.median(popular), statistics.median(unpopular)]
        stdevs = [statistics.stdev(popular), statistics.stdev(unpopular)]
        #create dataframe of results
        ks_df = pd.DataFrame({molecule:[means, medians, stdevs]}, index=properties) #create a dataframe with molecule
        final_df = pd.merge(ks_df, final_df, left_index=True, right_index=True) #merge new data frame with existing on properties
    final_df.drop('properties', axis=1, inplace=True)
    final_df.to_csv("Outputs/03 Outcomes/Statistics.tsv", sep='\t') #save as tsv

Categorizes (popular, absent, etc.) and counts the number of amines

In [6]:
def experiment_popularity(file="metaloxides"):
    """creates a file of count information on the experimental amines popularity"""
    metalborates = pd.read_table("Outputs/02 Popularity Files/"+file+"_popularity.tsv")
    absent = []
    unpopulars = []
    populars = []
    totals = []
    unique = []
    for exp in ['triangle', 'human']:
        experiment = pd.read_table("Outputs/03 Outcomes/"+exp+"_outcomestats.tsv")
        merge_df = pd.merge(metalborates, experiment, left_on='smiles', right_on='Smiles', how='right').fillna(-1)
        merge_df.to_csv(exp+"experimental_popularity.tsv", sep='\t') #save as tsv
        count_df = pd.DataFrame(merge_df.groupby(file+'_popular')[file+'_popular'].count())
        absent.append(count_df.iloc[0][file+'_popular'])
        unpopulars.append(count_df.iloc[1][file+'_popular'])
        populars.append(count_df.iloc[2][file+'_popular'])
        unique.append(len(experiment['Smiles'].tolist()))
        full = pd.read_table("Outputs/03 Outcomes/"+exp+"_clean.tsv")
        totals.append(len(full['Smiles'].tolist()))
    final_df = pd.DataFrame({'experiments_total':totals, 'amines_unique':unique, 'number_popular': populars, 'number_unpopular': unpopulars, 'number_absent': absent}, index=["triangle", "human"])
    final_df.to_csv("Outputs/03 Outcomes/experimental_popularity_analysis_"+file+".tsv", sep='\t') #save as tsv
In [21]:
def outcomes(outcomes_file, success_rate=False, include_absent_amines=False):
    for file in outcomes_file:
        clean_salts(file)
        outcome_success("Outputs/03 Outcomes/"+file[18:-12]+"_clean.tsv")
    success_popularity("Outputs/03 Outcomes/"+outcomes_file[1][18:-12]+"_outcomestats.tsv", success_rate)
    stats("Outputs/03 Outcomes/"+outcomes_file[1][18:-12]+"_clean.tsv", include_absent_amines) #update code for both human and triangle like experiment
    experiment_popularity()
    
outcomes(["Data/Experimental/human_borates.txt", "Data/Experimental/triangle_borates.txt"])